本文是对 Android 正向开发的过程记录。

基本上是零基础第一次看安卓相关的知识,因此此文仅仅是查阅了网上的一些博客,将一些必要的内容进行了整理和记录。其中还要很多知识并未完全消化理解。但记无妨,待日后有一定的基础时再来细细琢磨。

Android 四大组件

Android 开发的四大组件分别是:

活动(Activity),用于表现功能;

服务(Service),后台运行服务,不提供界面呈现;

广播接受者(Broadcast Receive),用于接收广播;

内容提供者(Content Provider),支持多个应用中存储和读取数据,相当于数据库

Activity

  1. Activity 之间通过 Intent 进行通信
  2. 安卓应用的每一个 Activity 都必须要在 AndroidManifest.xml 配置文件中进行生命,否则系统将不会识别,同时也不会执行此 Activity。
  3. Activity 会被压入栈中,一共有四种生命周期。运行中;暂停;停止;销毁;
    • 值得注意的是,暂停与停止的区别。
    • 暂停是当前 Activity 被另外的(非全屏的,或者透明的)挡住了,有别的 Activity 在前面。于是他无法与用户进行交互了,但是他的各种资源都还在内存中,除非系统的内存资源极低,系统才会去销毁此 Activity。
    • 停止是当前的 Activity 被另一个 Activity 完全覆盖了或者用户按下 HOME 返回主菜单的时候,就会进去的。当系统的别的地方需要用到内容时,系统会自动的去销毁 Activity。

Service

它通常用作在后台处理耗时的逻辑,与Activity一样,它存在自己的生命周期,也需要在AndroidManifest.xml配置相关信息。

服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。

服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。与某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。另外.也不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。

service用于在后台完成用户指定的操作。Service 分为两种:

  1. started(启动):当应用程序组件(如 Activity)调用 startService() 方法启动服务时,服务处于 started 状态。
  2. bound(绑定):当应用程序组件调用 bindService() 方法绑定到服务时,服务处于 bound 状态。

startService()bindService() 区别:

  1. started service(启动服务)是由其他组件调用 startService() 方法启动的,这导致服务的onStartCommand() 方法被调用。当服务是 started 状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后调用 stopSelf() 方法停止,或者由其他组件调用 stopService() 方法停止。
  2. 使用 bindService() 方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。

Service 组件通常用于为其他组件提供后台服务或监控其他组件的运行状态。

注意:Service 的 onCreate() 是在主线程(ActivityThread)中调用的,耗时操作会阻塞 UI, 如果需要做耗时的操作,则应该考虑使用 多线程或者 handler。

IntentService

是否知道 IntentService,在什么场景下使用 IntentService?

IntentService 相比父类 Service 而言,最大特点是其回调函数onHandleIntent中可以直接进行耗时操作,不必再开线程。其原理是 IntentService 的成员变量 Handler在初始化时已属于工作线程,之后handleMessage,包括 onHandleIntent 等函数都运行在工作线程中。

如果对 IntentService 的了解仅限于此,会有种 IntentService 很鸡肋的观点,因为在 Service 中开线程进行耗时操作也不麻烦。我当初也是这个观点,所以很少用 IntentService。

但是 IntentService 还有一个特点,就是多次调用 onHandleIntent 函数(也就是有多个耗时任务要执行),多个耗时任务会按顺序依次执行。原理是其内置的 Handler 关联了任务队列,Handler 通过 looper 取任务执行是顺序执行的。

这个特点就能解决多个耗时任务需要顺序依次执行的问题。而如果仅用 Service,开多个线程去执行耗时操作,就很难管理。

Broadcast Receive

在 Android 中,广播是一种广泛运用的在应用程序之间传输信息的机制。而广播接收器是对发送出来的广播进行过滤接受并响应的一类组件。可以使用广播接收器来让应用对一个外部事件做出响应。例如,当电话呼入这个外部事件到来时,可以利用广播接收器进行处理。

广播接收器既可以在 AndroidManifest.xml 中注册(静态注册),也可以在运行时的代码中使用 Context.registerReceive()(动态注册)进行注册。只要是注册了,当事件来临时,即使程序没有启动,系统也在需要的时候启动程序。

各种应用还可以通过使用 Context.sendBroadcast() 将它们自己的 Intent 广播给其他应用程序。

应用可以使用它对外部事件进行过滤,只对感兴趣的外部事件(如当电话呼入时,或者数据网络可用时)进行接收并做出响应。Broadcast Receive 没有用户界面。然而,它们可以启动一个 Activity 或 Serice 来响应它们收到的信息,或者用 NotificationManager 来通知用户。通知可以用很多种方式来吸引用户的注意力,例如闪动背灯、震动、播放声音等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。

动态注册的特点是当用来注册的 Activity 关掉后,广播也就失效了。静态注册无需担忧 Broadcast Receive 是否被关闭,只要设备是开启状态,广播接收器也是打开着的。也就是说哪怕 app 本身未启动,该 app 订阅的广播在触发时也会对它起作用。

Content Provider

Android 平台提供了 Content Provider 使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过 ContentResolver 类从该内容提供者中获取或存入数据。

只有需要在多个应用程序间共享数据是才需要 Content Provider。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式。

ContentProvider 实现数据共享。ContentProvider 用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为 Android 没有提供所有应用共同访问的公共存储区。

开发人员不会直接使用 ContentProvider 类的对象,大多数是通过 ContentResolver 对象实现对 ContentProvider 的操作。

ContentProvider 使用 URI 来唯一标识其数据集,这里的 URI 以 content:// 作为前缀,表示该数据由 ContentProvider 来管理。

Android 其他

Android 中的任务(Activity 栈)

任务其实就是 Activity 的栈,它由一个或多个 Activity 组成,共同完成一个完整的用户体验。栈底的是启动整个任务的 Activity,栈顶的是当前运行的用户可以交互的 Activity,当一个 Activity 启动另外一个的时候,新的 Activity 就被压入栈,并成为当前运行的 Activity。而前一个 Activity 仍保持在栈之中。当用户按下 BACK 键的时候,当前 Activity 出栈,而前一个恢复为当前运行的 Activity 。栈中保存的其实是对象,栈中的 Activity 永远不会重排,只会压入或弹出。

任务中的所有 Activity 是作为一个整体进行移动的。整个的任务(即 Activity 栈)可以移到前台,或退至后台。

Android 系统是一个多任务 (Multi-Task) 的操作系统,可以在用手机听音乐的同时,也执行其他多个程序。每多执行一个应用程序,就会多耗费一些系统内存(开辟了一个新栈),当同时执行的程序过多,或是关闭的程序没有正确释放掉内存,系统就会觉得越来越慢,甚至不稳定。为了解决这个问题,Android 引入了一个新的机制,即生命周期(Life Cycle)。

Android 清单文件

就我查阅到的资料显示,此清单文件是在文件 AndroidManifest.xml 中的一些字段和玩意。

文件结构

大概文件是长这个样子:

1
2
3
4
5
6
7
8
9
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="string"
android:sharedUserId="string"
android:sharedUserLabel="string resource"
android:versionCode="integer"
android:versionName="string"
android:installLocation=["auto"|"internalOnly"|"preferExternal"]>
.............
</manifest>

参数说明

xmlnspackage 是必须填写的值,而其余字典均是可选字段。

1.xmlns:android 属性——定义命名空间

我个人的理解是,这句代码是定义了一个自定义变量,名字就是冒号后面的 android。 而此变量要与后面的链接地址指向的包相关联。

格式如下:

1
xmlns:<命名空间标识>="http://schemas.android.com/apk/res/<完整的包名>"

xmlns:命名空间属性

android:标识

“http://schemas.android.com/apk/res/<完整的包名>”:命名空间的 URL

http://schemas.android.com/apk/res/:固定前缀

<完整的包名>:资源所在包名

一旦标识的名字被我们确定了,后面的 sharedUserId , versionCode ,等等前面的冒号前面的名字都需要统一改变,不然其无法认识。

2.package 属性——应用程序的身份证

package 属性唯一标识了一个应用程序。注意,它是唯一的!同样,它也是应用程序进程的默认名字以及应用程序中每个 Activity 的默认任务(taskAffinity)。

当我们生产一个 java 目录下的文件后,Android Studio 会自动根据其中的 package 名字来命名这个文件。

运行程序,此时 Android 设备就会为这个应用启动一个名字为 package 的进程。

3.android:sharedUserId 属性——共享数据

该属性定义了需要和其他应用程序共享的 Linux 用户 ID, 默认情况下,Android系统为每一个应用程序分配一个唯一的用户ID。(这里我的理解是,Android 本身就是一个 Linux 的系统,所以它通过这个 Linux 的用户 id 来进行用户权限的设定和共享,从而实现了应用程序之间的共享。)当这个属性在多个应用程序中被设置为相同值的时候,它们将共享一个用户ID。这样做的好处是,它们之间可以相互访问彼此的数据,如有需要,它们还将在相同的进程中运行。

与 android:sharedUserId 属性相关的属性还有 android:sharedUserLabel,这个属性给共享的用户ID定义了一个用户可读的标签。这个标签必须用字符串资源来设置,不能使用原生的字符串。这个属性在 API LEVEL3中引用,只有设置了 sharedUserId 属性时才有意义。

4.android:versionCode 属性——内部版本号

android:versionCode 属性的值是一个内部版本号,用于确定这个版本是否比另一个版本更新,数字越大表明它就越新。它不是显示给用户看的版本号,而是由 versionName 属性设置的号码。版本号将决定一些服务的行为,比如替换应用程序时是否执行备份还原操作等。

该号码必须设为整数,如100。此外,我们可以随心所欲地定义这个整数,只要每个继任的版本能有一个更大的数字即可。

5.android:versionName 属性——显示给用户的版本号

android:versionName 属性的值是显示给用户的版本号,它可以被设置为一个原始字符串或者一个字符串资源的引用。这个字符串除了要显示给用户外,没有其他的目的。

6.android:installLocation 属性——安装位置

该属性定义了应用程序默认的安装位置,共有3个可选值,其形式如下:

android:installLocation=[“auto”|”internalOnly”|”preferExternal”]

下表说明三个参数各自的意义。

描述
auto 应用程序可能被安装到外部存储设备中,但默认情况下系统将会把应用程序安装到内部存储设备中。如果内存不足,那么系统将会把应用程序安装到外部存储设备中。
internalOnly 应用程序必须安装到设备的内部存储设备中。如果设置了这个值,那意味着应用程序将永远不会安装到外部存储设备中去。如果内存不足,那么系统将不会安装这个APK。在没有设置 android:installLocation 属性的情况下,internalOnly 是该属性的默认值。
preferExternal 应用程序将会被安装到外部存储设备中,如果系统不支持外部存储设备或者外部设备已满,那么系统将会把这个应用程序安装到内部存储设备中。

Android res、raw 和 assets 的区别和使用

各目录文件存放的文件

assets:用于存放需要打包到应用程序的静态文件,以便部署到设备中。与 res/raw 不同点在于,ASSETS支持任意深度的子目录。这些文件不会生成任何资源ID,必须使用 /assets 开始(不包含它)的相对路径名。拥有更高的自由度,尽量不受 Android 平台的限制。这个目录中的文件除了不会被编译成二进制形式之外,另外一点就是,访问方式是通过文件名,而不是资源ID。

res:用于存放应用程序的资源(如图标、GUI布局等),将被打包到编译后的Java中。不支持深度子目录

res/menu:存放基于 XML 的菜单描述;

res/raw:存放通用的文件, 该文件夹内的文件将不会被编译成二进制文件,按原样复制到设备上。

res/values:存放字符串、尺寸值。

res/xml: 存放通用的 XML 文件。

Android 主题

有两种方法可以改变 app 的外观。

第一种就是直接在 xml 中直接修改 View 的属性。这种方法只适合于只有几个 View 和 Activity 的简单 app。

第二种方法就是创建自定义的样式和主题。

对应 web 开发,第一种方法类似于使用内联的 CSS 样式,而第二种类似于使用 style sheets。

style 和 theme:是一个包含一种 或者 多种格式化 属性 的集合 ,并且 style 和 theme 都是资源,存放在res/values 文件夹下

style:View 级别的,只能在某个 Activity 的布局文件中使用
Theme:应用级别的,你必须在 AndroidManifest.xml 中 的 \ 或者 \ 中使用

如何创建自定义的样式和主题和如何使用Android Studio的工具和快捷方式来加快样式的创建 请继续查阅文章: Android从零开始:创建样式和主题

Android 的 Intent 使用方法

Intent

Intent 的概念:

Android 中提供了 Intent 机制来协助应用间的交互与通讯,或者采用更准确的说法是,Intent 不仅可用于应用程序之间,也可用于应用程序内部的 activity, service 和 broadcast receiver 之间的交互。Intent 这个英语单词的本意是“目的、意向、意图”。

Intent 是一种运行时绑定(runtime binding) 机制,它能在程序运行的过程中连接两个不同的组件。通过 Intent,你的程序可以向 Android 表达某种请求或者意愿, Android 会根据意愿的内容选择适当的组件来响应。

四大组件中,Activity、Service 和 Broadcast Receiver 之间是通过 Intent 进行通信的,而另外一个组件 Content Provider 本身就是一种通信机制,不需要通过 Intent。

Intent 用法和机制

Intent 的两种基本用法:一种是显式的 Intent,即在构造 Intent 对象时就指定接收者,这种方式与普通的函数调用类似;另一种是隐式的 Intent,即 Intent 的发送者在构造 Intent 对象时,并不知道接收者是谁,只是指出接收者的一些特性(比如说启动音乐播放软件)。官方建议使用隐式Intent。

三种不同组件中发送时有不同的机制:

  • 使用 Context.startActivity()Activity.startActivityForResult() ,传入一个 Intent 来启动一个 Activity。使用 Activity.setResult() ,传入一个 Intent 来从 Activity 中返回结果;
  • 将 Intent 对象传给 Context.startService() 来启动一个 Service 或者传消息给一个运行的 Service, 将 Intent 对象传给 Context.bindService() 来绑定一个 Service;
  • 将 Intent 对象传给Context.sendBroadcast()Context.sendOrderedBroadcast() ,或者Context.sendStickyBroadcast() 等广播方法,则它们被传给 Broadcast Receiver。

Intent 相关属性

Intent由以下各个组成部分:

  • Component(组件):目的组件
  • Action(动作):用来表现意图的行动
  • Category(类别):用来表现动作的类别
  • Data(数据):表示与动作要操纵的数据
  • Type(数据类型):对于data范例的描写
  • Extras(扩展信息):扩展信息
  • Flags(标志位):期望这个意图的运行模式

上述属性中,Component 属性为直接类型,其他均为间接类型。

相比与显式 Intent,隐式 Intnet 则含蓄了许多,它并不明确指出我们想要启动哪一个活动,而是指定一系列更为抽象的 Action 和 Category 等信息,然后交由系统去分析这个 Intent,并帮我们找出合适的活动去启动。

Activity 中 Intent Filter 的匹配过程 :

加载安装所有的 Intent Filter 到一个列表中剔除 Action 匹配缺失的 Intent Filter剔除 URI 数据匹配失败的 Intent Filter剔除 Category 匹配失败的 Intent Filter余下的 Intent Filter 数量是否为 0是,则返回优先级最高的 Intent Filter否,则抛出查找异常

Component (组件):目的组件

Component 属性明确指定 Intent 的目标组件的类名称。(属于直接 Intent)

如果 Component 这个属性有指定的话,将直接使用它指定的组件。指定了这个属性以后,Intent 的其它所有属性都是可选的。

例如,启动第二个 Activity 时,我们可以这样来写:

1
2
3
4
5
6
7
8
9
10
11
button1.setOnClickListener(new OnClickListener() {            
@Override
public void onClick(View v) {
//创建一个意图对象
Intent intent = new Intent();
//创建组件,通过组件来响应
ComponentName component = new ComponentName(MainActivity.this, SecondActivity.class);
intent.setComponent(component);
startActivity(intent);
}
});

如果写的简单一点,监听事件 onClick()方法里可以这样写:

1
2
3
4
5
6
7
8
9
10
11
button1.setOnClickListener(new OnClickListener() {            
@Override
public void onClick(View v) {
Intent intent = new Intent();
//setClass函数的第一个参数是一个Context对象
//Context是一个类,Activity是Context类的子类,也就是说,所有的Activity对象,都可以向上转型为Context对象
//setClass函数的第二个参数是一个Class对象,在当前场景下,应该传入需要被启动的Activity类的class对象
intent.setClass(MainActivity.this, SecondActivity.class);
startActivity(intent)
}
});

再简单一点,可以这样写:(当然,也是最常见的写法)

1
2
3
4
5
6
7
button1.setOnClickListener(new OnClickListener() {            
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
}
});

Action(动作):用来表现意图的行动

当日常生活中,描述一个意愿或愿望的时候,总是有一个动词在其中。比如:我想“做”三个俯卧撑;我要“写” 一封情书,等等。在 Intent 中,Action 就是描述做、写等动作的,当你指明了一个 Action,执行者就会依照这个动作的指示,接受相关输入,表现对应行为,产生符合的输出。在 Intent 类中,定义了一批量的动作,比如 ACTION_VIEWACTION_PICK 等, 基本涵盖了常用动作。加的动作越多,越精确。

Action 是一个用户定义的字符串,用于描述一个 Android 应用程序组件,一个 Intent Filter 可以包含多个 Action。在 AndroidManifest.xml 的 Activity 定义时,可以在其 <intent-filter > 节点指定一个 Action 列表用于标识 Activity 所能接受的“动作”。

Category(类别):用来表现动作的类别

Category 属性也是作为<intent-filter> 子元素来声明的。例如:

1
2
3
4
5
<intent-filter>
  <action android:name="com.vince.intent.MY_ACTION"></action>
  <category android:name="com.vince.intent.MY_CATEGORY"></category>
  <category android:name="android.intent.category.DEFAULT"></category>
</intent-filter>

Action 和 Category 通常是放在一起用的,所以这里一起介绍一下。我们来先来举一个例子:

新建一个工程文件 smyh006_Intent01,在默认文件的基础之上,新建文件 SecondActicity.java 和 activity_second.xml。

紧接着,我们要到清单文件中进行注册,打开 AndroidManifest.xml,添加 SecondActivity 的 Action 和 Category 的过滤器:

1
2
3
4
5
6
7

<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.example.smyh006intent01.MY_ACTION"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

上方代码,表示 SecondActicity 可以匹配第4行的 MY_ACTION 这个动作,此时,如果在其他的 Acticity 通过这个 Action 的条件来查找,那 SecondActicity 就具备了这个条件。类似于相亲时,我要求对方有哪些条件,然后对方这个 SecondActicity 恰巧满足了这个条件(够通俗了吧)。

注:如果没有指定的 Category,则必须使用默认的 DEFAULT(即上方第5行代码)。

也就是说:只有 <action><category> 中的内容同时能够匹配上 Intent 中指定的 Action 和 Category 时,这个活动才能响应 Intent。如果使用的是 DEFAULT 这种默认的 Category,在稍后调用 startActivity() 方法的时候会自动将这个 Category 添加到 Intent 中。

现在来修改 MainActivity.java 中按钮的点击事件,代码如下:

1
2
3
4
5
6
7
8
9
10
button1.setOnClickListener(new OnClickListener() {            
@Override
public void onClick(View v) {
//启动另一个Activity,(通过action属性进行查找)
Intent intent = new Intent();
//设置动作(实际action属性就是一个字符串标记而已)
intent.setAction("com.example.smyh006intent01.MY_ACTION"); //方法:Intent android.content.Intent.setAction(String action)
startActivity(intent);
}
});

上方代码中,也可以换成下面这种简洁的方式:

1
2
3
4
5
6
7
8
button1.setOnClickListener(new OnClickListener() {            
@Override
public void onClick(View v) {
//启动另一个Activity,(通过action属性进行查找)
Intent intent = new Intent("com.example.smyh006intent01.MY_ACTION");//方法: android.content.Intent.Intent(String action)
startActivity(intent);
}
});

上方第5行代码:在这个 Intent 中,并没有指定具体哪一个 Activity,只是指定了一个 Action 的常量。所以说,隐式 Intent 的作用就表现的淋漓尽致了。此时,点击 MainActicity 中的按钮,就会跳到 SecondActicity 中去。

上述情况只有 SecondActicity 匹配成功。如果有多个组件匹配成功,就会以对话框列表的方式让用户进行选择。我们来详细介绍一下:

我们新建文件 ThirdActicity.java 和 activity_third.xml,然后在清单文件 AndroidManifest.xml 中添加ThirdActivity 的 Action 和 Category 的过滤器:

1
2
3
4
5
6
<activity android:name=".ThirdActivity">
<intent-filter>
<action android:name="com.example.smyh006intent01.MY_ACTION"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

此时,运行程序,当点击 MainActivity 中的按钮时,弹出一个选择应用的界面。

相信大家看到了这个界面,应该就一目了然了。于是我们可以做出如下总结:

在自定义动作时,使用 Activity 组件时,必须添加一个默认的类别

具体的实现为:

1
2
3
4
<intent-filter>
<action android:name="com.example.action.MY_ACTION"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

如果有多个组件被匹配成功,就会以对话框列表的方式让用户进行选择。

每个 Intent 中只能指定一个 Action,但却能指定多个 Category;类别越多,动作越具体,意图越明确(类似于相亲时,给对方提了很多要求)。

目前我们的 Intent 中只有一个默认的 Category,现在可以通过 intent.addCategory() 方法来实现。修改MainActivity 中按钮的点击事件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
button1.setOnClickListener(new OnClickListener() {            
@Override
public void onClick(View v) {
//启动另一个Activity,(通过action属性进行查找)
Intent intent = new Intent();
//设置动作(实际action属性就是一个字符串标记而已)
intent.setAction("com.example.smyh006intent01.MY_ACTION"); //方法:Intent android.content.Intent.setAction(String action)
intent.addCategory("com.example.smyh006intent01.MY_CATEGORY");
startActivity(intent);
}
});

既然在Intent中增加了一个 Category,那么我们要在清单文件中去声明这个 Category,不然程序将无法运行。代码如下:

1
2
3
4
5
6
7
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.example.smyh006intent01.MY_ACTION"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.example.smyh006intent01.MY_CATEGORY" />
</intent-filter>
</activity>

Data(数据):表示与动作要操纵的数据

  • Data 属性是 Android 要访问的数据,和 Action 和 Category 声明方式相同,也是在清单文件中。
  • 多个组件匹配成功显示优先级高的; 相同显示列表。

Data 是用一个 uri 对象来表示的,uri 代表数据的地址,属于一种标识符。通常情况下,我们使用Action+Data 属性的组合来描述一个意图:做什么。

使用隐式 Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得 Android 多个应用程序之间的功能共享成为了可能。比如应用程序中需要展示一个网页,没有必要自己去实现一个浏览器(事实上也不太可能),而是只需要调用系统的浏览器来打开这个网页就行了。

【实例】打开指定网页:

MainActivity.java中,监听器部分的核心代码如下:

1
2
3
4
5
6
7
8
9
10
button1.setOnClickListener(new OnClickListener() {            
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri data = Uri.parse("http://www.baidu.com");
intent.setData(data);
startActivity(intent);
}
});

当然,上方代码也可以简写成:

1
2
3
4
5
6
7
8
button1.setOnClickListener(new OnClickListener() {            
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}
});

第4行代码:指定了 Intent 的 Action 是 Intent.ACTION_VIEW,表示查看的意思,这是一个 Android 系统内置的动作;

第5行代码:通过 Uri.parse() 方法,将一个网址字符串解析成一个 Uri 对象,再调用 Intent 的 setData() 方法将这个 Uri 对象传递进去。

当点击按钮时,将跳到浏览器打开百度的页面。

此时, 调用的是系统默认的浏览器,也就是说,只调用了这一个组件。现在如果有多个组件得到了匹配,应该是什么情况呢?

我们修改修改清单文件中对SecondAcivity的声明:

1
2
3
4
5
6
7
8
<activity 
android:name=".SecondActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" android:host="www.baidu.com"/>
</intent-filter>
</activity>

现在,SecondActivity 也匹配成功了,我们运行程序,点击 MainActicity 的按钮时,弹出选择浏览器的界面供我们选择。

我们可以总结如下:

  • 当 Intent 匹配成功的组件有多个时,显示优先级高的组件,如果优先级相同,显示列表让用户自己选择
  • 优先级从-1000至1000,并且其中一个必须为负的才有效

注:系统默认的浏览器并没有做出优先级声明,其优先级默认为正数。

优先级的配置如下:

在清单文件中修改对 SecondAcivity 的声明,即增加一行代码,通过来 android:priority 设置优先级,如下:

1
2
3
4
5
6
7
8
<activity 
android:name=".SecondActivity">
<intent-filter android:priority="-1">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" android:host="www.baidu.com"/>
</intent-filter>
</activity>

Data 属性的声明中要指定访问数据的 Uri 和 MIME 类型。可以在元素中通过一些属性来设置:

android:scheme、android:path、android:port、android:mimeType、android:host 等,通过这些属性来对应一个典型的 Uri 格式 scheme://host:port/path。例如:http://www.google.com

type(数据类型):对于data范例的描写

如果 Intent 对象中既包含 Uri 又包含 Type,那么,在 <intent-filter> 中也必须二者都包含才能通过测试。

Type 属性用于明确指定 Data 属性的数据类型或 MIME 类型,但是通常来说,当 Intent 不指定 Data 属性时,Type 属性才会起作用,否则 Android 系统将会根据 Data 属性值来分析数据的类型,所以无需指定 Type 属性。

Data 和 Type 属性一般只需要一个,通过 setData() 方法会把 Type 属性设置为 null,相反设置 setType() 方法会把 Data 设置为 null,如果想要两个属性同时设置,要使用 Intent.setDataAndType() 方法。

【任务】:Data+Type 属性的使用 【实例】:播放指定路径的mp3文件。

具体如下:

新建工程文件 smyh006_Intent02,MainActivity.java 中按钮监听事件部分的代码如下:

1
2
3
4
5
6
7
8
9
10
11
button.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri data = Uri.parse("file:///storage/sdcard0/平凡之路.mp3");
//设置data+type属性
intent.setDataAndType(data, "audio/mp3"); //方法:Intent android.content.Intent.setDataAndType(Uri data, String type)
startActivity(intent);
}
});

第6行:”file://“ 表示查找文件,后面再加上我的小米手机存储卡的路径:/storage/sdcard0 ,再加上具体歌曲的路径。

第8行:设置 Data+Type 属性 

运行后,当点击按钮时,会显示一个播放音乐的界面,使用小米默认的音乐播放器。

Extras(扩展信息):扩展信息

是其它所有附加信息的集合。使用 Extras 可以为组件提供扩展信息,比如,如果要执行“发送电子邮件”这个

动作,可以将电子邮件的标题、正文等保存在 Extras 里,传给电子邮件发送组件。

Flags(标志位):期望这个意图的运行模式

一个程序启动后系统会为这个程序分配一个 task 供其使用,另外同一个 task 里面可以拥有不同应用程序的Activity 。那么,同一个程序能不能拥有多个 task?这就涉及到加载 Activity 的启动模式,这个需要单独讲一下。

注:Android 中一组逻辑上在一起的 Activity 被叫做 task,自己认为可以理解成一个 Activity 堆栈。

Activity 的启动模式:(面试注意)

Activity 有四种启动模式:standard、singleTop、singleTask、singleInstance。可以在AndroidManifest.xml 中 Activity 标签的属性 android:launchMode 中设置该 Activity 的加载模式。

  • standard 模式:默认的模式,以这种模式加载时,每当启动一个新的活动,必定会构造一个新的 Activity实例放到返回栈(目标task)的栈顶,不管这个 Activity 是否已经存在于返回栈中;
  • singleTop 模式:如果一个以 singleTop 模式启动的 Activity 的实例已经存在于返回桟的桟顶,那么再启动这个 Activity 时,不会创建新的实例,而是重用位于栈顶的那个实例,并且会调用该实例的onNewIntent() 方法将 Inten t对象传递到这个实例中;

注:如果以 singleTop 模式启动的 Activity 的一个实例已经存在于返回桟中,但是不在桟顶,那么它的行为和 standard 模式相同,也会创建多个实例;

  • singleTask 模式:这种模式下,每次启动一个 Activity 时,系统首先会在返回栈中检查是否存在该活动的实例,如果存在,则直接使用该实例,并把这个活动之上的所有活动统统清除;如果没有发现就会创建一个新的活动实例;
  • singleInstance 模式:总是在新的任务中(那就是新开一个栈了)开启,并且这个新的任务中有且只有这一个实例,也就是说被该实例启动的其他 Activity 会自动运行于另一个任务中。当再次启动该 Activity 的实例时,会重新调用已存在的任务和实例。并且会调用这个实例的 onNewIntent() 方法,将 Intent 实例传递到该实例中。和 singleTask 相同,同一时刻在系统中只会存在一个这样的 Activity 实例。(singleInstance 即单实例)

注:前面三种模式中,每个应用程序都有自己的返回栈,同一个活动在不同的返回栈中入栈时,必然是创建了新的实例。而使用 singleInstance 模式可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪一个应用程序来访问这个活动,都公用同一个返回栈,也就解决了共享活动实例的问题。(此时可以实现任务之间的切换,而不是单独某个栈中的实例切换)(我没懂,是每个 APP 都启动一个单独的栈吗?还是一堆逻辑相近的 Activity 一起租用一个公用栈?还是都有?)

其实我们不在清单文件中设置,只在代码中通过flag来设置也是可以的,如下:

1
2
3
4
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
//相当于singleTask
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
1
2
3
4
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
//相当于singleTop
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

更多

包括 Intent 常见应用等参见 参考博客

参考链接

Android 四大组件参考:https://blog.csdn.net/xchaha/article/details/80398620

Android 清单文件参考:https://blog.csdn.net/liyuanjinglyj/article/details/46866521

res、raw和assets参考:https://www.jb51.net/article/77904.htm

Android 主题参考一:http://www.jcodecraeer.com/a/basictutorial/2016/0812/6533.html

Android 主题参考二:https://blog.csdn.net/geduo_83/article/details/86561559

Android Intent 用法:https://www.cnblogs.com/zhouheng0918/p/9209327.html